package com.meisen.springboot.service.impl;

import com.meisen.springboot.entity.AchieveData;
import com.meisen.springboot.entity.Article;
import com.meisen.springboot.repo.ArticleRepository;
import com.meisen.springboot.service.ArticleService;
import com.meisen.springboot.service.redis.*;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Service
@Transactional
public class ArticleServiceImpl implements ArticleService {

    private static final String NULL = "null";

    private final ArticleRepository articleRepository;
    private final ArticleRedisService articleRedisService;
    // 已发布索引
    private final ArticlePublishRedisService articlePublishRedisService;
    // tag索引
    private final ArticleTagRedisService articleTagRedisService;
    // 访问量 Redis sortSet
    private final ArticleVisitRedisService articleVisitRedisService;
    // 点赞
    private final ArticleComplimentRedisService articleComplimentRedisService;
    // 归档
    private final ArticleAchieveRedisService articleAchieveRedisService;

    @Autowired
    public ArticleServiceImpl(ArticleRepository articleRepository, ArticleRedisService articleRedisService,
                              ArticlePublishRedisService articlePublishRedisService, ArticleTagRedisService articleTagRedisService,
                              ArticleVisitRedisService articleVisitRedisService, ArticleComplimentRedisService articleComplimentRedisService, ArticleAchieveRedisService articleAchieveRedisService) {
        this.articleRepository = articleRepository;
        this.articleRedisService = articleRedisService;
        this.articlePublishRedisService = articlePublishRedisService;
        this.articleTagRedisService = articleTagRedisService;
        this.articleVisitRedisService = articleVisitRedisService;
        this.articleComplimentRedisService = articleComplimentRedisService;
        this.articleAchieveRedisService = articleAchieveRedisService;
    }

    @Override
    public List<Article> findAll() {
        Set<String> ids = articleVisitRedisService.all();
        // 按照时间排序
        List<Article> articles = ids.stream()
                .map(articleRedisService::get)
                .sorted(Comparator.comparing(Article::getDate))
                .collect(Collectors.toList());
        // 如果没有从缓存取到数据， 就从数据库中获取，保存到缓存中
        if (isNullOrEmpty(articles)) {
            articles = articleRepository.findAll();
            articles.stream().distinct().forEach(this::cacheData);
        }
        return articles;
    }

    @Override
    public Map<String, List<AchieveData>> achieveArticle() {
        Map<String, List<AchieveData>> achieveMap = new HashMap<>();
        Set<String> keys = articleAchieveRedisService.getKeys();
        if (isNullOrEmpty(keys)) {
            List<Article> articles = articleRepository.findAll();
            articles.stream().distinct().forEach(this::cacheAchieveData);
            keys = articleAchieveRedisService.getKeys();
        }
        keys.forEach(key -> {
            List<AchieveData> achieveData = articleAchieveRedisService.get(key);
            achieveMap.put(key, achieveData.stream()
                    .sorted(Comparator.comparing(AchieveData::getDate).reversed())
                    .collect(Collectors.toList()));
        });
        return achieveMap;
    }

    @Override
    public Article publishArticle(Article article) {
        Article newArticle = articleRepository.save(article);
        newArticle.setUpdated(new Date());
        newArticle.setDate(new Date());
        // 将新文章添加到缓存
        cacheData(newArticle);
        return newArticle;
    }

    @Override
    public void deleteArticle(String id) {
        articleRepository.deleteById(id);
        articleVisitRedisService.removeZSet(id);
        // 由于这里可能涉及到标签的更新， 为了操作简单，还是就直接empty
        articleTagRedisService.emptyHash();
        articleAchieveRedisService.emptyHash();
        articleRedisService.remove(id);
    }

    @Override
    public Article updateArticle(Article article) {
        Article newArticle = articleRepository.saveAndFlush(article);
        Article oldArticle = getArticleById(article.getId());
        // 设置发布时间
        newArticle.setDate(oldArticle.getDate());
        newArticle.setUpdated(new Date());
        Integer visit = getArticleVisit(article.getId());
        newArticle.setVisit(visit);
        // 将新文章添加到缓存
        articleRedisService.put(article.getId(), newArticle, -1);
        if (article.isPublish()) {
            articlePublishRedisService.sAdd(new String[]{article.getId()}, -1L);
        }
        // 由于这里可能涉及到标签的更新， 为了操作简单，还是就直接empty
        articleTagRedisService.emptyHash();
        articleAchieveRedisService.emptyHash();
        return article;
    }

    @Override
    public void increaseVisit(String id, Integer visit) {
        // 更新访问量
        articleRepository.updateArticleVisitById(visit, id);
        Article article = getArticleById(id);
        article.setVisit(visit);
        // 数据库中查询到了，就缓存到数据库
        articleVisitRedisService.add(article.getId(), article.getVisit());
        articleRedisService.put(article.getId(), article, -1);
    }

    @Override
    public void compliment(String id, Integer compliment) {
        //更新点赞数
        articleRepository.updateArticleComplimentById(compliment, id);
        Article article = getArticleById(id);
        article.setCompliment(compliment);
        // 数据库中查询到了，就缓存到数据库
        articleComplimentRedisService.add(article.getId(), article.getCompliment());
        articleRedisService.put(article.getId(), article, -1);
    }

    @Override
    public void cancelCompliment(String id, Integer compliment) {
        articleRepository.updateArticleComplimentById(compliment, id);
        Article article = getArticleById(id);
        article.setCompliment(compliment);
        // 数据库中查询到了，就缓存到数据库
        articleComplimentRedisService.add(id, compliment);
        articleRedisService.put(id, article, -1);
    }

    @Override
    public Integer getArticleVisit(String id) {
        return (int) articleVisitRedisService.score(id);
    }

    @Override
    public Integer getArticleCompliment(String id) {
        return (int) articleComplimentRedisService.score(id);
    }

    @Override
    public Article getArticleById(String id) {
        Article article = articleRedisService.get(id);
        // 如果获取不到就从数据库中获取
        if (article == null) {
            article = articleRepository.findById(id).orElseGet(() -> {
                Article tempArticle = new Article();
                tempArticle.setId(Article.NO_SUCH_ARTICLE);
                return tempArticle;
            });
            if (!Article.NO_SUCH_ARTICLE.equals(article.getId())) {
                // 数据库中查询到了，就缓存到数据库
                cacheData(article);
            }
        }
        return article;
    }

    @Override
    public Long countArticle(boolean publish) {
        Set<String> set = articlePublishRedisService.getAllSet();
        return set.stream().map(articleRedisService::get).count();
    }

    @Override
    public Integer countArticle() {
        return articlePublishRedisService.getAllSet().size();
    }

    @Override
    public List<Article> findPopularArticles(Integer page, Integer size) {
        Set<String> ids = articleVisitRedisService.sort(page, size);
        List<Article> articles = ids.stream().map(articleRedisService::get).collect(Collectors.toList());
        return isNullOrEmpty(articles) ? articleRepository.findAllByIdNotNullOrderByVisitDesc(PageRequest.of(page, size)) : articles;
    }

    @Override
    public List<Article> findPartArticles(int page, int size) {
        Set<String> ids = articleVisitRedisService.all();
        List<Article> articles = ids.stream()
                .map(articleRedisService::get)
                .filter(Article::isPublish)
                .sorted(Comparator.comparing(Article::getDate).reversed())
                .limit(size + 1)
                .collect(Collectors.toList());
        return isNullOrEmpty(articles) ? articleRepository.findAllByIdNotNullOrderByDateDesc(PageRequest.of(page, size)) : articles;
    }

    @Override
    public List<Article> findRelativeArticles(String tags, int page, int size) {
        List<String> tagList = Arrays.asList(tags.split(","));
        List<Article> relativeArticles = getRelativeArticlesFromCache(tagList);
        // 如果没有从缓存中取到数据
        if (isNullOrEmpty(relativeArticles)) {
            tagList.forEach(v -> {
                String tag = "%" + v + "%";
                List<Article> articles = articleRepository.findArticlesByTagsLikeOrderByDateDesc(tag, PageRequest.of(page, size));
                relativeArticles.addAll(articles);
            });
        }
        return relativeArticles.stream()
                .sorted(Comparator.comparing(Article::getDate).reversed())
                .distinct()
                .limit(size + 1)
                .collect(Collectors.toList());
    }

    @Override
    public List<String> getAllArticleTags() {
        Set<String> allTags = articleTagRedisService.getKeys();
        if (isNullOrEmpty(allTags)) {
            List<Article> articles = articleRepository.findAll();
            allTags = articles.stream()
                    .flatMap(this::cacheTags)
                    .distinct()
                    .collect(Collectors.toSet());
        }
        return allTags.stream().map(this::replaceWithSpace).collect(Collectors.toList());
    }

    /**
     * 缓存tag内容
     *
     * @param article 需要缓存的文章
     * @return 缓存后的字符流
     */
    private Stream<? extends String> cacheTags(Article article) {
        // 如果缓存中没有，就需要从数据库中获取，并保存到缓存中
        Arrays.stream(article.getTags().split(",")).map(this::replaceSpace).forEach(tag -> {
            // 当hash中存在改tag的时候，通过','号合并
            String values = articleTagRedisService.get(tag);
            String value = Strings.isNotBlank(values) ? values + "," : "";
            // 如果value中已存在该id，就不需要重复缓存
            if (!value.contains(article.getId())) {
                articleTagRedisService.put(tag, value + article.getId(), -1);
            }
        });
        return Arrays.stream(article.getTags().split(","));
    }

    /**
     * 从缓存和中获取相关文章
     *
     * @param tags 标签list
     * @return relativeArticles
     */
    private List<Article> getRelativeArticlesFromCache(List<String> tags) {
        StringBuilder ids = new StringBuilder();
        List<Article> relativeArticles = new ArrayList<>();
        tags.stream().map(this::replaceWithSpace).forEach(tagItem -> {
            String values = articleTagRedisService.get(tagItem);
            if (!Strings.isBlank(values)) {
                ids.append(values);
                ids.append(",");
            }
        });
        List<String> cacheIds = Arrays.asList(ids.toString().split(","));
        cacheIds.stream().filter(Strings::isNotBlank).filter(id -> !NULL.equals(id)).forEach(id -> relativeArticles.add(articleRedisService.get(id)));
        return relativeArticles;
    }

    /**
     * 缓存文章到Redis
     *
     * @param article 文章
     */
    private void cacheData(Article article) {
        articleVisitRedisService.add(article.getId(), article.getVisit());
        articleRedisService.put(article.getId(), article, -1);
        // 缓存标签
        cacheTags(article);
        //缓存归档
        cacheAchieveData(article);
        if (article.isPublish()) {
            articlePublishRedisService.sAdd(new String[]{article.getId()}, -1L);
        }
    }


    /**
     * 缓存归档文章
     * @param article 文章
     */
    private void cacheAchieveData(Article article) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(article.getDate());
        String year = String.valueOf(calendar.get(Calendar.YEAR));
        AchieveData achieveData = new AchieveData();
        achieveData.setDate(article.getDate());
        achieveData.setId(article.getId());
        achieveData.setTitle(article.getTitle());
        if (!articleAchieveRedisService.isKeyExists(year)) {
            articleAchieveRedisService.put(year, new ArrayList<>(Collections.singleton(achieveData)), -1);
            return;
        }
        // 当hash中存在该year的时候
        List<AchieveData> values = articleAchieveRedisService.get(year);
        if (values.contains(achieveData)){
            return;
        }
        values.add(achieveData);
        articleAchieveRedisService.put(year, values, -1);
    }

    /**
     * 判断集合是否为空
     *
     * @param collection 集合
     * @return true: 为空  false: 不为空
     */
    private boolean isNullOrEmpty(Collection<?> collection) {
        return null == collection || collection.isEmpty();
    }

    /**
     * 用空格替换
     *
     * @param decodeText 字符串
     * @return 替换后的字符串
     */
    public String replaceWithSpace(String decodeText) {
        return String.format(decodeText, " ");
    }

    /**
     * 替换空格
     * 通过占位符的形式开替换
     *
     * @param origin 原始字符串
     * @return 替换后的字符串
     */
    public String replaceSpace(final String origin) {
        return origin.replaceAll(" ", "%s");
    }
}
